//
// printoutput.cpp
// libprotea
// Tools Library for the Protea Project / Server-Side Reporting
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

#include "../defines.h"
#include "../Buffer/Buffer.h"
#include "../Fields/fields.h"
#include "../OSDep/transport.h"
#include "../OSDep/files.h"
#include "../OSDep/utf8.h"
#include "../AppHandler/AppInterface.h"
#include "../xmlhelper.h"
#include "../addons/smtp.h"
#include "../defns.h"
#include "printoutput.h"
using namespace ProteaApp1;

const char* report_file = "reports.xml";

//
// Xerces xml parser
//
#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <xercesc/util/XMLString.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>
using namespace XERCES_CPP_NAMESPACE;

//
// Xalan stylesheet transformer
//
#include <xalanc/XalanTransformer/XalanTransformer.hpp>
#include <xalanc/XalanTransformer/XercesDOMWrapperParsedSource.hpp>
#include <xalanc/XercesParserLiaison/FormatterToXercesDOM.hpp>
#include <xalanc/XercesParserLiaison/XercesParserLiaison.hpp>
#include <xalanc/XercesParserLiaison/XercesDOMSupport.hpp>
#include <xalanc/XMLSupport/FormatterToXML.hpp>
#include <xalanc/XMLSupport/FormatterToDOM.hpp>
#include <xalanc/XSLT/XSLTInputSource.hpp>
#include <xalanc/XalanDOM/XalanDOMString.hpp>

//XALAN_USING_XERCES(DOMDocument)
XALAN_USING_XERCES(DOMImplementation)

XALAN_USING_XALAN(FormatterToXML)
XALAN_USING_XALAN(FormatterToXercesDOM)
XALAN_USING_XALAN(XalanDOMString)
XALAN_USING_XALAN(XalanTransformer)
XALAN_USING_XALAN(XercesDOMSupport)
XALAN_USING_XALAN(XercesDOMWrapperParsedSource)
XALAN_USING_XALAN(XercesParserLiaison)
XALAN_USING_XALAN(XSLTInputSource)
XALAN_USING_XALAN(XSLTResultTarget)

ReportLink* XCPrintOutput::links = NULL;


// Internal structure for maintain a list of open tags
struct OutputTag {
	char* name;
	OutputTag* next;
};



XCPrintOutput::XCPrintOutput( XCAppInterface* owner ) {
	current_report = NULL;
	TagList = NULL;
	app = owner;
	parser = NULL;
};


XCPrintOutput::~XCPrintOutput() {
	if (parser)
		delete parser;
};



XercesDOMParser* init_parser = NULL;

// Initialize the print system by reading the reports file
void XCPrintOutput::Initialize() {
//	printf( "INIT REPORTS\n" );
	if (XCFileExists( report_file )) {
//		printf( "FOUND REPORT FILE\n" );
		if (!init_parser)
			init_parser = new XercesDOMParser();
		DOMTreeConsoleErrorReporter* errReporter = new DOMTreeConsoleErrorReporter();
		init_parser->setErrorHandler( errReporter );
		init_parser->setIncludeIgnorableWhitespace( false );
		init_parser->parse( report_file );
		XERCES_CPP_NAMESPACE::DOMNode* node = init_parser->getDocument()->getFirstChild();
		while (node) {
			UniTranslator nname( node->getNodeName() );
			if (strcmp( nname.UTF8(), "Reports" )==0) {
				node = node->getFirstChild();
			} else if (strcmp( nname.UTF8(), "Report" )==0) {
				XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = node->getAttributes();
				for (unsigned int i=0; i<attrib->getLength(); i++) {
					XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
					if (anode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ATTRIBUTE_NODE) {
						UniTranslator name( anode->getNodeName() );
						UniTranslator value( anode->getNodeValue() );

						if (strcmp( name.UTF8(), "Number" )==0) {
							ReportLink* link = new ReportLink;
							link->node = node;
							link->id = atoi( value.UTF8() );
							link->next = links;
							links = link;
						};
					};
				};
				node = node->getNextSibling();
			} else
				node = node->getNextSibling();
		};
	};
};


void XCPrintOutput::Uninitialize() {
	ReportLink* temp;
	while (links) {
		temp = links;
		links = links->next;
		delete temp;
	};
};


bool XCPrintOutput::Start( int id ) {
	// Determine if there is any need to create this report
	ReportLink* rtemp = links;
	while (rtemp && rtemp->id != id) {
		rtemp = rtemp->next;
	};

	current_report = rtemp;

	// If we are creating the report:
	if (current_report) {
//		printf( "CREATE REPORT\n" );
		UniTranslator rootname( "root" );
		UniTranslator corename( "Core" );

		// Create the internal DOM object for the data
		try {
			DataDOC = DOMImplementationRegistry::getDOMImplementation( corename )->createDocument( 0, rootname, 0 );
			current_node = DataDOC->getDocumentElement();
			assert( current_node );
		} catch( DOMException dom) {
			UniTranslator msg( dom.msg );
			printf( "DOM Exception: %d %s\n", dom.code, msg.UTF8() );
			assert( false );
		} catch(... ) {
			assert( false );
		};
	} else {
		DataDOC = NULL;
		printf( "REPORT NOT DEFINED\n" );
	};

	// Let the caller know if we are creating a report or not
	return (current_report != NULL);
};



void XCPrintOutput::CreatePrintFile( output_style style, DOMNode* node, bool print ) {
	char* input_file = "foo-new.xml";

	INFUNC( XCPrintOutput::CreatePrintFile, app );

	XMLCh tempStr[100];
	XMLString::transcode("LS", tempStr, 99);
	DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
	DOMWriter* theSerializer = ((DOMImplementationLS*)impl)->createDOMWriter();
	XMLFormatTarget *myFormatTarget = new LocalFileFormatTarget( input_file );

	// Turn on serializer "pretty print" option
	if ( theSerializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true) )
  		theSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

	// Turn this node into a new document so that we get namespace info
	DOMNode* child = node->getFirstChild();

	//
	// Create a new document that with only the PDF information
	//

	// Start with a new document with garbage root node
	UniTranslator rootname( "garbage" );
	XERCES_CPP_NAMESPACE::DOMDocument* newdoc = DOMImplementation::getImplementation()->createDocument( 0, rootname, 0 );

	// Replace the garbage node with the node directly below "PDF"
	DOMNode* clone = newdoc->importNode( child, true );
	newdoc->replaceChild( clone, newdoc->getDocumentElement() );

	bool first = true;
	bool hasns = false;
	DOMNode* newnode = clone;
	do {
		// Append the new node, if necessary
		if (!first) {
			newnode = newdoc->importNode( child, true );
			newdoc->appendChild( newnode );
		} else
			first = false;

		UniTranslator nname( child->getLocalName() );
		if (strcmp( nname.UTF8(), "root" )==0) {
			// Create the name of the attribute
			UniTranslator prefix( child->getPrefix() );
			char* temp = new char[ strlen( prefix.UTF8() ) + 7 ];
			sprintf( temp, "xmlns:%s", prefix.UTF8() );

			// Create an attribute for the namespace
			UniTranslator URI( "http://www.w3.org/1999/XSL/Format" );
			UniTranslator NSURI( "http://www.w3.org/2000/xmlns/" );
			UniTranslator ns( temp );

			DOMNode* attr = newdoc->createAttributeNS( NSURI, ns );
			attr->setNodeValue( URI );

			newnode->getAttributes()->setNamedItemNS( attr );
			delete [] temp;
		};

		child = child->getNextSibling();
	} while (child);

	// Write the document
	theSerializer->writeNode( myFormatTarget, *newdoc );

	// Flush the buffer to ensure all contents are written
	myFormatTarget->flush();

	// release the memory
	theSerializer->release();
	delete myFormatTarget; 

	// Determine how to call FOP
	char* output_file = NULL;
	char* options = NULL;

	if (style == PS_OUTPUT) {
		output_file = "foo-out.ps";
		options = "-ps";
	} else if (style == PDF_OUTPUT) {
		output_file = "foo-out.pdf";
	} else {
		printf( "UNSUPPORTED OUTPUT STYLE\n" );
		return;
	};

	// Now output the file
	if (app->fop_location && XCFileExists( app->fop_location )) {
		int fop_result;
		char file[ MAX_PATH ];

		PRINT_OUTPUT_TO_FILE;

		if (fop_result != 0) {
			// We had an error message
			RAISETOOLSERROR_1PARAM( ERR_FOP_ERROR, ERR_FOP_FAILURE );
		};
	} else {
		// Send a message to the system admin
		app->AdminMsg( ERR_FOP_ERROR, 0, 1, ERR_FOP_LOCATION );

		// Send a message to the user
		RAISETOOLSERROR_1PARAM( ERR_FOP_ERROR, ERR_FOP_LOCATION );
	};

	OUTFUNC();
};



void XCPrintOutput::HandleStyleSheet( const char* style, const char* filename ) {
	INFUNC( XCPrintOutput::HandleStyleSheet, app );

    XalanTransformer theXalanTransformer;
	if (!filename)
		filename = "foo-out.xml";

	int result = theXalanTransformer.transform("testfile.xml", style, filename);
	if (result) {
//		printf( "IT FAILED: %d\n", result );
		assert( false );
	};

	XERCES_CPP_NAMESPACE::DOMDocument* doc;
	XERCES_CPP_NAMESPACE::XercesDOMParser *parser = new XERCES_CPP_NAMESPACE::XercesDOMParser;
	DOMTreeConsoleErrorReporter *errReporter = new DOMTreeConsoleErrorReporter();
	parser->setErrorHandler( errReporter );
	parser->setIncludeIgnorableWhitespace( false );
	parser->setDoNamespaces(true);    // optional
	parser->parse( "foo-out.xml" );

	// Trigger some events
	DOMNode* node = parser->getDocument()->getDocumentElement();
	while (node) {
		UniTranslator nname( node->getNodeName() );
		if (strcmp( nname, "SMTP" )==0) {
			DOMNode* smtp = node->getFirstChild();
			XCMessage msg;
			char* body = NULL;

			while (smtp) {
				UniTranslator sname( smtp->getNodeName() );
				if (strcmp( sname, "Subject" )==0) {
					UniTranslator value( smtp->getFirstChild()->getNodeValue() );
					msg.SetSubject( value.UTF8() );
				} else if (strcmp( sname, "From" )==0) {
					UniTranslator value( smtp->getFirstChild()->getNodeValue() );
					msg.SetFrom( value.UTF8() );
				} else if (strcmp( sname, "To" )==0) {
					DOMNode* recp = smtp->getFirstChild();
					while (recp) {
						UniTranslator tname( recp->getNodeName() );
						if (strcmp( tname, "Email" )==0) {
							UniTranslator value( recp->getFirstChild()->getNodeValue() );
							msg.AddRecp( value.UTF8() );
						};
						recp = recp->getNextSibling();
					};
				} else if (strcmp( sname, "Attach" )==0) {
					UniTranslator value( smtp->getFirstChild()->getNodeValue() );
					msg.AddAttachment( value.UTF8() );
				} else if (strcmp( sname, "Body" )==0) {
					XMLCh tempStr[100];
					XMLString::transcode("LS", tempStr, 99);
					DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr);

					// construct the DOMWriter
					DOMWriter* myWriter = ((DOMImplementationLS*)impl)->createDOMWriter();

					// serialize a DOMNode to a UTF-16 string
					if (myWriter->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true) )
  						myWriter->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

					XMLCh* theXMLString_Unicode = myWriter->writeToString(*smtp);
					UniTranslator bodystr( theXMLString_Unicode );
					body = strdup( bodystr.UTF8() );
					body[ strlen( body ) - 8 ] = 0;
					msg.SetData( body+7 );

					// release the memory
					XMLString::release(&theXMLString_Unicode); 
					myWriter->release(); 
				};
				smtp = smtp->getNextSibling();
			};

			try {
				msg.Send( app->smtp );
			} catch( XCSMTPException& exc ) {
				char result_msg[ 255 ];
				exc.GetDescription( result_msg );
				RAISETOOLSERROR_1PARAM( ERR_SMTP_ERROR, result_msg );
			};
		} else if (strcmp( nname, "Printer" )==0) {
			CreatePrintFile( PS_OUTPUT, node, true );
		} else if (strcmp( nname, "PDF" )==0) {
			CreatePrintFile( PDF_OUTPUT, node, false );
		} else if (strcmp( nname, "Program" )==0) {
			printf( "PROGRAM OUTPUT\n" );
		};
		node = node->getNextSibling();
	};

	OUTFUNC();
};



void XCPrintOutput::Finish() {
	if (current_report) {
		// Finish out any open tags
		OutputTag* temp = TagList;
		while (temp) {
			EndTag();
			temp = TagList;
		};

		// Write the xml file (testing)
		try {
			XMLCh tempStr[100];
			XMLString::transcode("LS", tempStr, 99);
			DOMImplementation *impl = DOMImplementationRegistry::getDOMImplementation(tempStr);
			DOMWriter* theSerializer = ((DOMImplementationLS*)impl)->createDOMWriter();
			XMLFormatTarget *myFormatTarget = new LocalFileFormatTarget("testfile.xml");

			// Turn on serializer "pretty print" option
			if ( theSerializer->canSetFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true) )
  				theSerializer->setFeature(XMLUni::fgDOMWRTFormatPrettyPrint, true);

			theSerializer->writeNode(myFormatTarget, *current_node);

			// Flush the buffer to ensure all contents are written
			myFormatTarget->flush();

			// release the memory
			theSerializer->release();
			delete myFormatTarget; 
		} catch( ... ) {
			printf( "******\nERROR\n*******\n" );
			assert( false );
		};

		// Transform each specified output
		XERCES_CPP_NAMESPACE::DOMNode* node = current_report->node;
		if (node)
			node = node->getFirstChild();

//		printf( "START OUTPUT\n" );
		while (node) {
			if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
				UniTranslator nname( node->getNodeName() );
				if (strcmp( nname.UTF8(), "Output" )==0) {
					char* style = NULL;
					char* filename = NULL;

					XERCES_CPP_NAMESPACE::DOMNamedNodeMap* attrib = node->getAttributes();
					for (unsigned int i=0; i<attrib->getLength(); i++) {
						XERCES_CPP_NAMESPACE::DOMNode* anode = attrib->item( i );
						if (anode->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ATTRIBUTE_NODE) {
							UniTranslator name( anode->getNodeName() );
							UniTranslator value( anode->getNodeValue() );

							if (strcmp( name.UTF8(), "StyleSheet" )==0)
								style = strdup( value.UTF8() );
							else if (strcmp( name.UTF8(), "Filename" )==0)
								filename = strdup( value.UTF8() );
						};
					};

					if (style) {
						// Tranform...
						HandleStyleSheet( style, filename );
						free( style );
					};

					if (filename)
						free( filename );
				};
			};

			if (node)
				node = node->getNextSibling();
		};

		// Drop the file
//		XCDeleteFile( filename );
	};
};



void XCPrintOutput::StartTag( const char* tag ) {
	if (current_report) {
		// Add a new node
		UniTranslator tagname( tag );
		DOMNode* new_node = DataDOC->createElement( tagname );

		// Append the node
		current_node->appendChild( new_node );
		current_node = new_node;

		// Write to the tag list
		OutputTag* otag = new OutputTag;
		otag->name = strdup( tag );
		otag->next = TagList;
		TagList = otag;
	};
};



void XCPrintOutput::StartTag( const int value ) {
	if (current_report) {
		const char* svalue = app->GetCatalogMsg( value, XCGetLanguage() );
		StartTag( svalue );
	};
};



void XCPrintOutput::EndTag() {
	if (current_report) {
		// Find the last tag
		if (!TagList)
			return;

		// Move up in the chain
		current_node = current_node->getParentNode();

		// Remove from the tag list
		OutputTag* tag = TagList;
		TagList = TagList->next;
	};
};




void XCPrintOutput::OutputBuffer( const XCBuffer* buffer ) {
	if (current_report) {
		// Output each field in the buffer
		for (int i=0; i<buffer->GetFieldCount(); i++) {
			OutputField( buffer->GetFieldByIndex( i ) );
		};
	};
};



void XCPrintOutput::OutputField( const XCField* field ) {
	if (current_report) {
		char buffer[ 340 ];

		// Create the field tag
		UniTranslator tagname( field->GetFieldName() );
		DOMNode* new_node = DataDOC->createElement( tagname.UTF16() );

		// Create the field data
		field->charoutput( buffer, 255, true );
		UniTranslator dataval( buffer );
		DOMNode* data_node = DataDOC->createTextNode( dataval.UTF16() );

		// Link everything up
		new_node->appendChild( data_node );
		current_node->appendChild( new_node );
	};
};
